/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */
#include "TransformGizmo.h"
#include "CameraObject.h"

namespace PQBuilder
{
	Ogre::String GizmoMaterialNames::MATERIAL_TRANSLATE_X="MATERIAL_TRANSLATE_X";
	Ogre::String GizmoMaterialNames::MATERIAL_TRANSLATE_Y="MATERIAL_TRANSLATE_Y";
	Ogre::String GizmoMaterialNames::MATERIAL_TRANSLATE_Z="MATERIAL_TRANSLATE_Z";

	Ogre::String GizmoMaterialNames::MATERIAL_ROTATE_X="MATERIAL_ROTATE_X";
	Ogre::String GizmoMaterialNames::MATERIAL_ROTATE_Y="MATERIAL_ROTATE_Y";
	Ogre::String GizmoMaterialNames::MATERIAL_ROTATE_Z="MATERIAL_ROTATE_Z";

	Ogre::String GizmoMaterialNames::MATERIA_SCALE_X="MATERIA_SCALE_X";
	Ogre::String GizmoMaterialNames::MATERIAL_SCALE_Y="MATERIAL_SCALE_Y";
	Ogre::String GizmoMaterialNames::MATERIAL_SCALE_Z="MATERIAL_SCALE_Z";
	Ogre::String GizmoMaterialNames::MATERIAL_NONE="MATERIAL_NONE";

#define GIZMO_MESH_TRANSLATE		"Geometry/Primitive/Gizmo/Translate";
#define GIZMO_MESH_ROTATE			"Geometry/Primitive/Gizmo/Rotate";
#define GIZMO_MESH_SCALE				"Geometry/Primitive/Gizmo/Scale";


	Ogre::String getGizmoMeshName(int type)
	{
		switch(type){
			case GIZMO_TRANSLATE_X:
			case GIZMO_TRANSLATE_Y:
			case GIZMO_TRANSLATE_Z:
				return GIZMO_MESH_TRANSLATE;
			case GIZMO_ROTATE_X:
			case GIZMO_ROTATE_Y:
			case GIZMO_ROTATE_Z:
				return GIZMO_MESH_ROTATE;
			case	GIZMO_SCALE_X:
			case	GIZMO_SCALE_Y:
			case	GIZMO_SCALE_Z:
				return GIZMO_MESH_SCALE;
		}
		return GIZMO_MESH_TRANSLATE;
	}

	Ogre::String getGizmoMaterialName(int select)
	{
		switch(select){
			case GIZMO_TRANSLATE_X:
				return GizmoMaterialNames::MATERIAL_TRANSLATE_X;
			case GIZMO_ROTATE_X:
				return GizmoMaterialNames::MATERIAL_TRANSLATE_Y;
			case GIZMO_SCALE_X:
				return GizmoMaterialNames::MATERIAL_TRANSLATE_Z;
			case GIZMO_TRANSLATE_Y:
				return GizmoMaterialNames::MATERIAL_ROTATE_X;
			case GIZMO_ROTATE_Y:
				return GizmoMaterialNames::MATERIAL_ROTATE_Y;
			case GIZMO_SCALE_Y:
				return GizmoMaterialNames::MATERIAL_ROTATE_Z;
			case GIZMO_TRANSLATE_Z:
				return GizmoMaterialNames::MATERIA_SCALE_X;
			case GIZMO_ROTATE_Z:
				return GizmoMaterialNames::MATERIAL_SCALE_Y;
			case GIZMO_SCALE_Z:
				return GizmoMaterialNames::MATERIAL_SCALE_Z;
		}
		return GizmoMaterialNames::MATERIAL_NONE;
	}

	Ogre::ColourValue getGizmoColor(int select)
	{
		switch(select){
			case GIZMO_TRANSLATE_X:
			case GIZMO_ROTATE_X:
			case GIZMO_SCALE_X:
				return Ogre::ColourValue(0.5,0,0);
			case GIZMO_TRANSLATE_Y:
			case GIZMO_ROTATE_Y:
			case GIZMO_SCALE_Y:
				return Ogre::ColourValue(0,0.5,0);
			case GIZMO_TRANSLATE_Z:
			case GIZMO_ROTATE_Z:
			case GIZMO_SCALE_Z:
				return Ogre::ColourValue(0,0,0.5);
		}
		return Ogre::ColourValue(0.5,0.5,0.5);
	}

	Ogre::ColourValue getGizmoHighlightColor(int select)
	{
		switch(select){
			case GIZMO_TRANSLATE_X:
			case GIZMO_ROTATE_X:
			case GIZMO_SCALE_X:
				return Ogre::ColourValue(1,0,0);
			case GIZMO_TRANSLATE_Y:
			case GIZMO_ROTATE_Y:
			case GIZMO_SCALE_Y:
				return Ogre::ColourValue(0,1,0);
			case GIZMO_TRANSLATE_Z:
			case GIZMO_ROTATE_Z:
			case GIZMO_SCALE_Z:
				return Ogre::ColourValue(0,0,1);
		}
		return Ogre::ColourValue(1,1,1);
	}


	Ogre::MaterialPtr TransformGizmo::_materials[GIZMO_OP_COUNT];

	bool TransformGizmo::_meshLoaded=false;
	TransformGizmo::TransformGizmo(PQEngine::SceneObject* sceneObject)
		: Object(), _sceneObject(sceneObject),_transforming(false),_show(false),
		_scale(Ogre::Vector3(1,1,1)),_position(Ogre::Vector3(0,0,0)),_operation(GIZMO_OP_NONE)
	{
		assert(_sceneObject);

		_meshData=new PQEngine::MeshData();
		_sceneMgr=_sceneObject->getSceneManager();
		
		createGizmo();
		show(_show);
	}

	void TransformGizmo::setGizmoMode(GizmoMode mode)
	{
		_mode=mode;

		for(int i=0;i<GIZMO_OP_COUNT;i++){
			_nodes[i]->setVisible(false);
		}

		if(!_show){
			return;
		}

		switch(mode){
			case GIZMO_MODE_SELECT:
				_operation=GIZMO_OP_NONE;
				break;
			case GIZMO_MODE_TRANSLATE:{
				_nodes[GIZMO_TRANSLATE_X]->setVisible(true);
				_nodes[GIZMO_TRANSLATE_Y]->setVisible(true);
				_nodes[GIZMO_TRANSLATE_Z]->setVisible(true);
				break;
			}case GIZMO_MODE_ROTATE:{
				_nodes[GIZMO_ROTATE_X]->setVisible(true);
				_nodes[GIZMO_ROTATE_Y]->setVisible(true);
				_nodes[GIZMO_ROTATE_Z]->setVisible(true);
				break;
			}case GIZMO_MODE_SCALE:{
				_nodes[GIZMO_SCALE_X]->setVisible(true);
				_nodes[GIZMO_SCALE_Y]->setVisible(true);
				_nodes[GIZMO_SCALE_Z]->setVisible(true);	
				break;
			}
		}
	}


	
	void TransformGizmo::createGizmoMaterial()
	{

		for(int i=0;i<GIZMO_OP_COUNT;i++){		
			_materials[i]=Ogre::MaterialManager::getSingleton().create(getGizmoMaterialName(i),"General");
			_materials[i]->setReceiveShadows(false);
			//_materials[i]->setLightingEnabled(false);
			_materials[i]->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false);
			_materials[i]->getTechnique(0)->getPass(0)->setPolygonMode(Ogre::PM_SOLID);
			_materials[i]->getTechnique(0)->getPass(0)->setCullingMode(Ogre::CULL_NONE);
			//_materials[i]->getTechnique(0)->getPass(0)->setDiffuse(getGizmoColor(i));
			_materials[i]->getTechnique(0)->getPass(0)->setAmbient(0,0,0);
			_materials[i]->getTechnique(0)->getPass(0)->setSelfIllumination(getGizmoColor(i));
		}
	}

	void TransformGizmo::createGizmo()
	{
		if(!_meshLoaded){
			createGizmoMaterial();
			_meshLoaded=true;

		}

		_rootNode=_sceneMgr->getRootSceneNode()->createChildSceneNode();
		
		//
		Ogre::String entityName="GizmoEntity_"+Ogre::StringConverter::toString(getId());
		entityName=entityName+"_";
		for(int i=0;i<GIZMO_OP_COUNT;i++){
			Ogre::String name=entityName+Ogre::StringConverter::toString(i);
			_nodes[i]=_rootNode->createChildSceneNode();
			_entities[i] = _sceneMgr->createEntity(name, getGizmoMeshName(i));
			_entities[i]->setCastShadows(false);
			_entities[i]->setMaterial(_materials[i]);
			_entities[i]->setRenderQueueGroup(Ogre::RENDER_QUEUE_SKIES_LATE);
			_nodes[i]->attachObject(_entities[i]);
		}

		Ogre::Quaternion q1,q2;
		q1.FromAngleAxis(Ogre::Degree(90),Ogre::Vector3(0,0,1));
		q2.FromAngleAxis(Ogre::Degree(90),Ogre::Vector3(1,0,0));
		_nodes[GIZMO_TRANSLATE_Y]->setOrientation(q1*q2);
		_nodes[GIZMO_ROTATE_Y]->setOrientation(q1*q2);
		_nodes[GIZMO_SCALE_Y]->setOrientation(q1*q2);

		q1.FromAngleAxis(Ogre::Degree(-90),Ogre::Vector3(0,1,0));
		q2.FromAngleAxis(Ogre::Degree(-90),Ogre::Vector3(1,0,0));
		_nodes[GIZMO_TRANSLATE_Z]->setOrientation(q1*q2);
		_nodes[GIZMO_ROTATE_Z]->setOrientation(q1*q2);
		_nodes[GIZMO_SCALE_Z]->setOrientation(q1*q2);

	}

	void TransformGizmo::setPosition(Ogre::Vector3 pos)
	{
		_position=pos;
		_rootNode->setPosition(pos);
	}

	void TransformGizmo::setOrientation(Ogre::Quaternion orient)
	{
		_orient=orient;
		_rootNode->setOrientation(orient);
	}

	void TransformGizmo::setScale(Ogre::Vector3 scale)
	{
		_scale=scale;
		
	}

	void TransformGizmo::updateGizmo(Ogre::Vector3 cameraPosition)
	{

		float distance = (_position - cameraPosition).length();
        _rootNode->setScale(distance/20, distance/20, distance/20);
		
	}

	void TransformGizmo::restoreDefaultGizmo()
	{
		if(_transforming){
			return;
		}
		for(int i=0;i<GIZMO_OP_COUNT;i++){
			_materials[i]->getTechnique(0)->getPass(0)->setSelfIllumination(getGizmoColor(i));
		}
		_operation= GIZMO_OP_NONE;
	}

	void TransformGizmo::setGizmoOp(GizmoOp op)
	{
		if(_transforming){
			return;
		}
		_operation=op;
		_materials[op]->getTechnique(0)->getPass(0)->setSelfIllumination(getGizmoHighlightColor(op));
	}

	void TransformGizmo::show(bool show)
	{
		_show=show;
		if(!show){
			for(int i=0;i<GIZMO_OP_COUNT;i++){
				_nodes[i]->setVisible(false);
			}
		}else{
			setGizmoMode(_mode);
		}
	}

	void TransformGizmo::update(int x,int y)
	{
		show(_show);
		

		PQEngine::GameObject *obj=_sceneObject->getGameObject();
		//Terrain no need transform gizmo(can not transform).
		//You should transform mesh object rather than terrain object.
		if(!obj||obj->isTerrain()){//no object selected or terrain selected
			show(false);
			return;//none selected
		}

		if(_transforming){//while tranform
			//perform axis gizmo operation
			translate(x,y);
			float deltaX=(x-_mouseX)*0.5f;
			float deltaY=(y-_mouseY)*0.5f;
			float delta=0;
			if(deltaX>=deltaY){
				delta=deltaX;
			}else{
				delta=deltaY;
			}
			rotate(delta);
			scale(x,y);

			//really transform current selected game object
			obj->setPosition(_position);
			obj->setOrientation(_orient);
			obj->setScale(_scale);
		}else{
			setPosition(obj->getPosition());
			setScale(obj->getScale());
			setOrientation(obj->getOrientation());
		}

		//process axis gizmo before update
		Ogre::Vector3 dir=_sceneObject->getCamera()->getDirection();
		Ogre::Ray ray=_sceneObject->getRay(x,y);
		Ogre::Vector3 position=_sceneObject->getCamera()->getPosition();

		
		updateGizmo(position);
		updateGizmo(ray);

		show(true);
		_mouseX=x;
		_mouseY=y;
	}
	
	bool TransformGizmo::updateGizmo(Ogre::Ray ray)
	{
		restoreDefaultGizmo();

		int start=0;
		int end=-1;
		switch(_mode){
			case GIZMO_MODE_TRANSLATE:
				start=GIZMO_TRANSLATE_X;//no following loop
				end=GIZMO_TRANSLATE_Z;
				break;
			case GIZMO_MODE_ROTATE:
				start=GIZMO_ROTATE_X;//no following loop
				end=GIZMO_ROTATE_Z;
				break;
			case GIZMO_MODE_SCALE:
				start=GIZMO_SCALE_X;//no following loop
				end=GIZMO_SCALE_Z;
				break;
		}
		for(int i=start;i<=end;i++){
			std::pair<bool, Ogre::Real> hit = Ogre::Math::intersects(ray, _entities[i]->getWorldBoundingBox());
			if(!hit.first){
				continue;
			}
			//every time need reload,beacause gizmo transformed
			_meshData->loadMeshEntity(_entities[i],_entities[i]->getParentNode());
			if(_meshData->intersect(ray)){
				setGizmoOp((GizmoOp)i);
				return true;
			}
		}

		return false;
	}

	void TransformGizmo::stopTransform()
	{
		_transforming=false;
	}

	void TransformGizmo::startTransform(int x,int y)
	{
		if(_transforming){
			return;
		}
		
		Ogre::Vector3 dir=_sceneObject->getCamera()->getDirection();
		Ogre::Ray ray=_sceneObject->getRay(x,y);
		
		_translateDelta=getWorldProjectionPosition(dir,ray)-_position;
		_lastOrientation = _rootNode->getOrientation();
		
		_transforming=true;
		
	}

	bool TransformGizmo::isGizmoSelected()
	{
		if(_operation==GIZMO_OP_NONE||_operation==GIZMO_OP_COUNT){
			return false;
		}
		return true;
	}


	//get mouse ray projection position to gizmo plane in world coordinate
	Ogre::Vector3 TransformGizmo::getWorldProjectionPosition(Ogre::Vector3 cameraDirection,Ogre::Ray ray)
	{
		Ogre::Quaternion q = _rootNode->getOrientation();
		Ogre::Vector3 pos=_rootNode->getPosition();

		if(_operation==GIZMO_OP_NONE){
			_translationPlane=Ogre::Plane(-cameraDirection,pos);
		}else{
			Ogre::Plane planeX(q.xAxis(), pos);//actual planeYZ
			Ogre::Plane planeY(q.yAxis(), pos);
			Ogre::Plane planeZ(q.zAxis(), pos);

			float vX = planeX.projectVector(ray.getDirection()).length();
			float vY = planeY.projectVector(ray.getDirection()).length();
			float vZ = planeZ.projectVector(ray.getDirection()).length();

			switch(_operation){
				case GIZMO_TRANSLATE_X:
				case GIZMO_SCALE_X:
				case GIZMO_ROTATE_X:
					vX=10000.0f;
					break;
				case GIZMO_TRANSLATE_Y:
				case GIZMO_SCALE_Y:
				case GIZMO_ROTATE_Y:
					vY=10000.0f;
					break;
				case GIZMO_TRANSLATE_Z:
				case GIZMO_SCALE_Z:
				case GIZMO_ROTATE_Z:
					vZ=10000.0f;
					break;
			}

			if (vX < vY && vX < vZ){
				_translationPlane=planeX;
			}else{
				if (vY < vX && vY < vZ ){
					_translationPlane=planeY;
				}else{
					_translationPlane=planeZ;
				}
			}
		}
		
		std::pair<bool,Ogre::Real> result=ray.intersects(_translationPlane);

		if(result.first){
			Ogre::Vector3 AxisX = Ogre::Vector3::ZERO;
			Ogre::Vector3 AxisY = Ogre::Vector3::ZERO;
			Ogre::Vector3 AxisZ = Ogre::Vector3::ZERO;

			switch(_operation){
				case GIZMO_TRANSLATE_X:
				case GIZMO_SCALE_X:
				case	GIZMO_ROTATE_X:
					AxisX = q.xAxis();
					break;
				case GIZMO_TRANSLATE_Y:
				case GIZMO_SCALE_Y:
				case	GIZMO_ROTATE_Y:
					AxisY = q.yAxis();
					break;
				case GIZMO_TRANSLATE_Z:
				case GIZMO_SCALE_Z:
				case GIZMO_ROTATE_Z:
					AxisZ = q.zAxis();
					break;
				case GIZMO_OP_NONE:
				{
					AxisX = q.xAxis();
					AxisY = q.yAxis();
					AxisZ = q.zAxis();
					break;
				}
			}
			Ogre::Vector3 proj=ray.getPoint(result.second)-_position;
			Ogre::Vector3 posX=(AxisX.dotProduct(proj)*AxisX);
			Ogre::Vector3 posY=(AxisY.dotProduct(proj)*AxisY);
			Ogre::Vector3 posZ=(AxisZ.dotProduct(proj)*AxisZ);
			pos+=posX+posY+posZ;
		}

		return pos;
	}

	Ogre::Vector3 TransformGizmo::getPosition()
	{
		return _position;
	}
	
	Ogre::Quaternion TransformGizmo::getOrientation()
	{
		return _rootNode->getOrientation();
	}

	Ogre::Vector3 TransformGizmo::getScale()
	{
		return _scale;
	}

	//according current camera direction and camera ray to calc translation of axis gizmo
	void TransformGizmo::translate(int x,int y)
	{
		if(!_transforming||_mode!=GIZMO_MODE_TRANSLATE){
			return;
		}

		Ogre::Vector3 dir=_sceneObject->getCamera()->getDirection();
		Ogre::Ray ray=_sceneObject->getRay(x,y);
		Ogre::Vector3 pos=getWorldProjectionPosition(dir,ray)-_translateDelta;
		setPosition(pos);
	}

	void TransformGizmo::rotate(float delta)
	{
		if(!_transforming||_mode!=GIZMO_MODE_ROTATE){
			return;
		}
		Ogre::Quaternion q=_rootNode->getOrientation();
		switch(_operation){
			case GIZMO_ROTATE_X:
				q=q*Ogre::Quaternion(Ogre::Degree(-delta),Ogre::Vector3(1,0,0));
				break;
			case GIZMO_ROTATE_Y:
				q=q*Ogre::Quaternion(Ogre::Degree(-delta),Ogre::Vector3(0,1,0));
				break;
			case GIZMO_ROTATE_Z:
				q=q*Ogre::Quaternion(Ogre::Degree(-delta),Ogre::Vector3(0,0,1));
				break;
		}
		_rootNode->setOrientation(q);
		_orient=q;
	}

	void TransformGizmo::scale(int x,int y)
	{
		if(!_transforming||_mode!=GIZMO_MODE_SCALE){
			return;
		}

		Ogre::Vector3 dir=_sceneObject->getCamera()->getDirection();
		Ogre::Ray ray=_sceneObject->getRay(x,y);

		Ogre::Vector3 projectDelta=getWorldProjectionPosition(dir,ray)-_position;
		 float dist1 = projectDelta.length();
         float dist2 = _translateDelta.length();

		 if(dist1!=0&&dist2!=0){
			 if(dist1/dist2<0.01){
				 return;
			 }
			 switch(_operation){
				case GIZMO_SCALE_X:
					_scale.x *= (dist1 / dist2);//divide by zero error
					break;
				case GIZMO_SCALE_Y:
					_scale.y *= (dist1 / dist2);
					break;
				case GIZMO_SCALE_Z:
					_scale.z *= (dist1 / dist2);
					break;
			 }
		 }
	}
}
